#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
# - http://heavyz.blogspot.com/
# - http://buggarden.blogspot.com/
#
# Views for the AFCP webapp.
#
# Created on 2008-10-28.
# $Id$
#

import logging
import datetime
import random
import urllib

from django.template import loader
from django.template import Context
from django.http import HttpRequest
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.http import HttpResponseNotFound
from django.http import Http404

from google.appengine.api import memcache

from buggarden.apps.afcp import utils
from buggarden.apps.afcp import resources
from buggarden.apps.afcp import paristech
from buggarden.apps.afcp import planet

from buggarden.apps.afcp.config import Config
from buggarden.apps.afcp.models import MODELS_REVISION
from buggarden.apps.afcp.models import AFCPUserProfile
from buggarden.apps.afcp.models import AFCPAnnouncement

from buggarden.apps.afcp.models import AFCPUser
from buggarden.apps.afcp.models import get_current_user
from buggarden.apps.afcp.models import create_login_url
from buggarden.apps.afcp.models import create_logout_url


#---------------------------------------------------------------------------------------------------
# Language class
#---------------------------------------------------------------------------------------------------

class Language(object):
    
    LANG_SESSION_NAME = "django_language"
    
    ENGLISH_CODE = "en"
    FRENCH_CODE  = "fr"
    CHINESE_CODE = "zh-cn"
    
    DEFAULT_LANG_CODE    = ENGLISH_CODE
    SUPPORTED_LANG_CODES = ( ENGLISH_CODE, FRENCH_CODE, CHINESE_CODE, )
    
    
    def __init__(self, code, url, is_current):
        self.code = code
        self.url = url
        self.is_current = is_current
    
    @staticmethod
    def create_lang_url(redirect_url, lang_code):
        params = { "url" : redirect_url, "lang" : lang_code, }
        url = "%s/lang/?%s" % (Config.URL_PREFIX, urllib.urlencode(params))
        return url
    
    @staticmethod
    def get_lang(request):
        try:
            lang_code = request.session.get(Language.LANG_SESSION_NAME, None)
        except AttributeError:
            lang_code = Language.DEFAULT_LANG_CODE
        return lang_code
    
    @staticmethod
    def set_lang(request, lang_code):
        try:
            request.session[Language.LANG_SESSION_NAME] = lang_code
            return True
        except AttributeError:
            return False


#---------------------------------------------------------------------------------------------------
# Common functions
#---------------------------------------------------------------------------------------------------

def _get_common_context_dict(request):

    # User info.
    user = get_current_user()
    if not user:
        log_url  = create_login_url("%s/login_ok/%s" % (Config.URL_PREFIX, request.path,))
        log_text = "Login"
        is_admin = False
    else:
        log_url  = create_logout_url("%s/login_ok/%s" % (Config.URL_PREFIX, request.path,))
        log_text = "Logout"
        is_admin = (user.email() in Config.ADMINS)
    
    # Language.
    current_lang_code = Language.get_lang(request)
    languages = []
    for lang_code in Language.SUPPORTED_LANG_CODES:
        lang_url    = Language.create_lang_url(request.path, lang_code)
        is_current  = (lang_code == current_lang_code)
        languages.append(Language(lang_code, lang_url, is_current))
    
    # Request meta info.
    http_referer = request.META.get("HTTP_REFERER", "unknown_http_referer")
    remote_addr  = request.META.get("REMOTE_ADDR" , "unknown_remote_addr" )
    remote_host  = request.META.get("REMOTE_HOST" , "unknown_remote_host" )
    
    # Return the common context dict.
    return { "user_"            : user,
             "log_url_"         : log_url,
             "log_text_"        : log_text,
             "is_admin_"        : is_admin,
             "url_"             : Config.URL_PREFIX,
             "static_"          : Config.STATIC_PREFIX,
             "languages_"       : languages,
             "http_referer_"    : http_referer,
             "remote_addr_"     : remote_addr,
             "remote_host_"     : remote_host,
             "models_revision_" : MODELS_REVISION, }


def _redirect_to_error_page(error):
    params = { "error" : error, }
    url = "%s/error/?%s" % (Config.URL_PREFIX, urllib.urlencode(params))
    return HttpResponseRedirect(url)


#---------------------------------------------------------------------------------------------------
# Change current language and logged-in/logged-out redirect
#---------------------------------------------------------------------------------------------------

def change_language(request):
    "Change the current language."
    
    redirect_url = utils.get_param(request.REQUEST, "url" )
    lang_code    = utils.get_param(request.REQUEST, "lang")
    Language.set_lang(request, lang_code)
    return HttpResponseRedirect(redirect_url)


def login_ok(request, url):
    try:
        profile = get_current_user().profile()
        if profile:
            profile.count_login()
            profile.put()
    except:
        pass
    return HttpResponseRedirect(url)


def logout_ok(request, url):
    return HttpResponseRedirect(url)


#---------------------------------------------------------------------------------------------------
# Home page, 404 not-found page, error page and static pages
#---------------------------------------------------------------------------------------------------

def home(request):
    "Displays the AFCP home page."
    
    announcements = AFCPAnnouncement.gql("ORDER BY post_date DESC").fetch(5, 0)
    ctxt_dict = { "announcements" : announcements,
                  "ecoles"        : paristech.ECOLES.values(),
                  "universities"  : paristech.UNIVERSITIES.values(),
                  "feeds"         : planet.FEEDS,
                  "banner_image"  : random.choice(resources.BANNER_IMAGES), }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/home.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


def not_found(request):
    "Displays the 404 not-found page."
    
    ctxt_dict = { "url" : request.path, }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/not_found.html" % Config.APP_NAME)
    return HttpResponseNotFound(tmpl.render(ctxt))


def error_page(request):
    "Displays the error page."
    
    ctxt_dict = { "error" : utils.get_param(request.REQUEST, "error"), }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/error.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


def static_page(request, name, lang=None):
    "Displays the requested static page in a requested or the current language."
    
    STATIC_PAGES = ( "about", "contact", "todo" )
    
    if name not in STATIC_PAGES:
        return not_found(request)
    
    if lang is None:
        lang = Language.get_lang(request)
    if lang not in Language.SUPPORTED_LANG_CODES:
        lang = Language.DEFAULT_LANG_CODE
    ctxt_dict = { "page_name"    : name,
                  "lang_code"    : lang,
                  "ecoles"       : paristech.ECOLES.values(),
                  "universities" : paristech.UNIVERSITIES.values(), }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/static/%s_%s.html" % (Config.APP_NAME, name, lang))
    return HttpResponse(tmpl.render(ctxt))


#---------------------------------------------------------------------------------------------------

def announcements(request):
    "Displays all announcements."
    
    ANNOUNCEMENTS_PER_PAGE = 10
    query = AFCPAnnouncement.gql("ORDER BY post_date DESC")
    page_number = utils.get_param(request.REQUEST, "page")
    announcements = utils.EntityPage(query, ANNOUNCEMENTS_PER_PAGE, page_number)
    ctxt_dict = { "announcements" : announcements, }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/announcements.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


def announcement(request, id):
    "Displays the announcement page."
    
    announcement = AFCPAnnouncement.get_by_id(int(id))
    if announcement is None:
        return not_found(request)
    
    if request.method == "GET":
        ctxt_dict = { "error"        : utils.get_param(request.GET, "error"),
                      "info"         : utils.get_param(request.GET, "info"),
                      "announcement" : announcement, }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/announcement.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
        params = {}
        try:
            announcement.title       = utils.get_param(request.POST, "title")
            announcement.resume      = utils.get_param(request.POST, "resume")
            announcement.content     = utils.get_param(request.POST, "content")
            announcement.update_date = datetime.datetime.utcnow()
            announcement.put()
            info = "Announcement '%s' updated successfully." % id
            params["info"] = info
        except Exception, ex:
            error = "Failed to update announcement '%s': %s" % (id, str(ex))
            logging.error(error)
            params["error"] = error
        redirect_url = "%s/announcement/%s/?%s" % (Config.URL_PREFIX, id, urllib.urlencode(params))
        return HttpResponseRedirect(redirect_url)


def post_announcement(request):
    "Posts a new announcement."
    
    user = get_current_user()
    if user.profile() is None or not user.profile().is_editor():
        logging.error("Current user does not have permission to post an announcement.")
        return HttpResponseRedirect("%s/" % Config.URL_PREFIX) # TODO
    
    if request.method == "GET":
        ctxt_dict = _get_common_context_dict(request)
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/post_announcement.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
        try:
            announcement = AFCPAnnouncement( title=utils.get_param(request.POST, "title"),
                                             resume=utils.get_param(request.POST, "resume"),
                                             content=utils.get_param(request.POST, "content"),
                                             author=user.google_user() )
            announcement.put()
            id = announcement.key().id()
            return HttpResponseRedirect("%s/announcement/%s/" % (Config.URL_PREFIX, id))
        except Exception, ex:
            error = "Failed to post announcement: %s" % str(ex)
            logging.error(error)
            return _redirect_to_error_page(error)


def members(request):
    """Displays the AFCP members list."""
    
    MEMBERS_PER_PAGE = 20
    query = AFCPUserProfile.gql("WHERE deleted=:deleted ORDER BY name", deleted=False)
    page_number = utils.get_param(request.REQUEST, "page")
    members = utils.EntityPage(query, MEMBERS_PER_PAGE, page_number)
    ctxt_dict = { "members" : members, "paginator" : members, }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/members.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


def profile(request, uid):
    """Displays the member profile by UID."""
    
    profile = AFCPUserProfile.gql("WHERE uid=:uid", uid=uid).get()
    if profile is None:
        return not_found(request)
    
    if request.method == "GET":
        ctxt_dict = { "info"         : utils.get_param(request.GET, "info"),
                      "error"        : utils.get_param(request.GET, "error"),
                      "uid"          : uid,
                      "profile"      : profile,
                      "ecoles"       : paristech.ECOLES.values(),
                      "universities" : paristech.UNIVERSITIES.values(), }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/profile.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
    
        params = {}
        action = utils.get_param(request.POST, "action")
        try:
            if action == "edit":
                
                # Update basic information.
                profile.name = utils.get_param(request.POST, "name")
                profile.name_zh = utils.get_param(request.POST, "name_zh")
                profile.sex = utils.get_param(request.POST, "sex")
                birthday_str = utils.get_param(request.POST, "birthday")
                if birthday_str is not None:
                    profile.birthday = datetime.datetime.strptime(birthday_str, "%Y-%m-%d")
                else:
                    profile.birthday = None
                promo_str = utils.get_param(request.POST, "promo")
                if promo_str is not None:
                    profile.promo = int(promo_str)
                else:
                    profile.promo = None
                profile.ecole_id = utils.get_param(request.POST, "ecole_id")
                profile.univ_id = utils.get_param(request.POST, "univ_id")
                profile.alt_email = utils.get_param(request.POST, "alt_email")
                profile.tel = utils.get_param(request.POST, "tel")
                
                # Update profile photo.
                query_dict = request.POST.copy()
                query_dict.update(request.FILES)
                photo_dict = query_dict.get("photo", None)
                if photo_dict is not None:
                    profile.photo_type = utils.get_param(photo_dict, "content-type")
                    profile.photo_data = photo_dict.get("content")
                elif utils.get_param(request.POST, "delete_photo") == "1":
                    profile.photo_type = None
                    profile.photo_data = None
                
                # Update profile in datastore.
                profile.put()
                params["info"] = "Profile updated successfully."
            
            elif action == "delete":
                profile.deleted = True
                profile.put()
                params["info"] = "Profile deleted successfully."
            
            elif action == "restore":
                profile.deleted = False
                profile.put()
                params["info"] = "Profile restored successfully."
            
            else:
                raise Exception, "Unknown action '%s'." % action
        
        except Exception, ex:
            error = "Failed to perform action '%s': %s" % (action, str(ex))
            logging.error(error)
            params["error"] = error
        
        return HttpResponseRedirect("%s?%s" % (request.path, urllib.urlencode(params)))


def profile_photo(request, uid):
    
    profile = AFCPUserProfile.gql("WHERE uid=:uid", uid=uid).get()
    if profile is None:
        return not_found(request)
    
    if request.method == "GET":
        if profile.has_photo():
            return HttpResponse(profile.photo_data, mimetype=profile.photo_type)
        else:
            return not_found(request)


def my_profile(request):
    """If current user already has a profile, redirect to the profile page; otherwise, display the
    create profile page.
    """
    
    # Anonymous user should not have arrived to this page.
    user = get_current_user()
    if not user:
        return HttpResponseRedirect("%s/" % Config.URL_PREFIX)
    
    # If the user already has a profile, redirect to the profile page.
    if user.profile() is not None:
        return HttpResponseRedirect("%s/profile/%s/" % (Config.URL_PREFIX, user.profile().uid))
    
    # Create profile for the current user.
    if request.method == "GET":
        # Return the create profile form.
        ctxt_dict = { "error"        : utils.get_param(request.GET, "error"),
                      "ecoles"       : paristech.ECOLES.values(),
                      "universities" : paristech.UNIVERSITIES.values(), }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/create_profile.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
        # Create a profile for the current user.
        try:
            profile = AFCPUserProfile( key_name=user.email(),
                                       uid=utils.generate_random_id("U"),
                                       name=utils.get_param(request.POST, "name"),
                                       name_zh=utils.get_param(request.POST, "name_zh"),
                                       status=AFCPUserProfile.PENDING_STATUS )
            profile.put()
            return HttpResponseRedirect("%s/profile/%s/" % (Config.URL_PREFIX, profile.uid))
        except Exception, ex:
            error = "Failed to create user profile: %s" % str(ex)
            logging.error(error)
            params = { "error" : error, }
            return HttpResponseRedirect("%s?%s" % (request.path, urllib.urlencode(params)))


def random_profile(request):
    # TODO: count() and fetch() cannot handle a large amount of results.
    # This function should be redesigned to be more scalable.
    query = AFCPUserProfile.gql("")
    count = query.count()
    if count <= 0:
        return not_found(request)
    
    random_offset = random.choice(range(0, count))
    profile_list  = query.fetch(1, random_offset)
    if not profile_list:
        return not_found(request)
    else:
        profile_uid = profile_list[0].uid
        return HttpResponseRedirect("%s/profile/%s/" % (Config.URL_PREFIX, profile_uid))


def ecole(request, acronym):

    if request.method == "GET":
        MEMBERS_PER_PAGE = 20
        query = AFCPUserProfile.gql( "WHERE ecole_id=:acronym AND deleted=:deleted ORDER BY name",
                                     acronym=acronym,
                                     deleted=False )
        page_number = utils.get_param(request.REQUEST, "page")
        members = utils.EntityPage(query, MEMBERS_PER_PAGE, page_number)
        ctxt_dict = { "members" : members,
                      "ecole"   : paristech.ECOLES.get(acronym),
                      "ecoles"  : paristech.ECOLES.values(), }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/ecole.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))


def university(request, acronym):

    if request.method == "GET":
        MEMBERS_PER_PAGE = 20
        query = AFCPUserProfile.gql( "WHERE univ_id=:acronym AND deleted=:deleted ORDER BY name",
                                     acronym=acronym,
                                     deleted=False )
        page_number = utils.get_param(request.REQUEST, "page")
        members = utils.EntityPage(query, MEMBERS_PER_PAGE, page_number)
        ctxt_dict = { "members"      : members,
                      "university"   : paristech.UNIVERSITIES.get(acronym),
                      "universities" : paristech.UNIVERSITIES.values(), }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/university.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))


"""
def cv(request, uid, lang=None): # TODO: remove default None!
    "Displays the CV of the AFCP member."
    
    # Find the user profile by uid and the curriculum vitae by language code.
    profile = AFCPUserProfile.gql("WHERE uid=:uid", uid=uid).get()
    if profile is None:
        return not_found(request)
    if lang == "zh-cn":
        cv = profile.cv_zh
    elif lang == "fr":
        cv = profile.cv_fr
    else:
        cv = profile.cv_en
    
    if request.method == "GET":
    
        ctxt_dict = { "profile" : profile, "cv" : cv, }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/cv.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
    
        section = utils.get_param(request.POST, "section")
        # TODO:
        try:
            if section == "cv_basic_info":
                if cv.basic_info is None:
                    cv.basic_info = props.BasicInfo()
                cv.basic_info.name = utils.get_param(request.POST, "name")
                profile.put()
            else:
                for k in request.POST.keys():
                    v = utils.get_param(request.POST, k)
                    logging.debug("POST: %s = %s" % (k,v))
        except Exception, ex:
            # TODO:
            pass
        
        ctxt_dict = { "profile" : profile, "cv" : cv, }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/sections/%s.html" % (Config.APP_NAME, section))
        return HttpResponse(tmpl.render(ctxt))
"""


def planet_afcp(request):
    "Displays the planet AFCP page (a feeds aggregator)."
    
    ctxt_dict = { "feeds" : planet.FEEDS, }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("%s/planet.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


def admin(request):
    "Displays the admin console and handles the admin request."
    
    user = get_current_user()
    if user.email() not in Config.ADMINS:
        return HttpResponseRedirect("%s/" % Config.URL_PREFIX)
    
    if request.method == "GET":
    
        error = utils.get_param(request.GET, "error")
        info  = utils.get_param(request.GET, "info" )
        
        PROFILES_PER_PAGE = 20
        query = AFCPUserProfile.gql("")
        page_number = utils.get_param(request.REQUEST, "page")
        profiles = utils.EntityPage(query, PROFILES_PER_PAGE, page_number)
        
        ctxt_dict = { "info" : info, "error" : error, "profiles" : profiles, }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("%s/admin.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
    
        action = utils.get_param(request.POST, "action")
        error = None
        info  = None
        try:
            if action == "add_editor":
                email   = utils.get_param(request.POST, "email")
                profile = AFCPUserProfile.get_by_key_name(email)
                if profile is None:
                    raise Exception("Failed to find profile for user '%s'." % email)
                elif not profile.is_editor():
                    profile.add_editor()
                    profile.put()
                    info = "Editor role granted to user '%s'." % email
                    logging.info(info)
            
            else:
                raise Exception, "Unknown action '%s'." % action
        
        except Exception, ex:
            error = "Failed to process admin action '%s': %s" % (action, str(ex))
            logging.error(error)
        
        params = {}
        if error is not None:
            params["error"] = error
        if info is not None:
            params["info"] = info
        redirect_url = "%s/admin/?%s" % (Config.URL_PREFIX, urllib.urlencode(params))
        return HttpResponseRedirect(redirect_url)
            



